package com.yuefresh.sys;

import java.io.Serializable;
import java.util.List;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.yuefresh.core.HibernateInvoker;
import com.yuefresh.util.CookieUtil;

/**
 * This authenticator stores the currently logged in user in the session as
 * OSUser LoginUser objects.
 * 
 * It also provides for cookie logins and creates cookies if needed.
 * 
 */
@Service
public class Authenticator implements Serializable {
	private static final long serialVersionUID = 178954L;

	private Log log = LogFactory.getLog(Authenticator.class);

	@Autowired protected HibernateInvoker hibernateInvoker;
	@Autowired private UserManager userManager;

	private String LOGIN_COOKIE_KEY = "cookie_myivo";

	public static String COOKIE_ENCODING = "xYz";
	/**
	 * The key used to store the user object in the session
	 */
	public static String LOGIN_ERROR_MSG = "LOGIN_ERROR_MSG";
	public static String LOGIN_ERROR_CODE0 = "账号系统中不存在";
	public static String LOGIN_ERROR_CODE1 = "密码不匹配";
	public static String LOGIN_ERROR_CODE2 = "账号已经被冻结";
	public static String LOGIN_ERROR_CODE3 = "账号尚未激活";
	public static String LOGIN_ERROR_CODE9 = "账号已被系统回收";

	/** Default system user's user_id */
	public static final String DEFAULT_SYS_USER = "sysadm";

	public static String LOGGED_IN_KEY = "user";
	/**
	 * The key used to indicate that the user has logged out and session
	 * regarding of it containing a cookie is not logged in.
	 */
	public static String LOGGED_OUT_KEY = "logout_user";

	// the age of the autologin cookie - default = 1 year (in seconds)
	private static int AUTOLOGIN_COOKIE_AGE = 30 * 24 * 60 * 60;

	public boolean login(HttpServletRequest request,
			HttpServletResponse response, String username, String password)
			throws AuthenticatorException {
		return login(request, response, username, password, false);
	}

	/**
	 * Tries to authenticate a user (via OSUser). If successful, sets a session
	 * attribute and cookie indicating their logged-in status.
	 * 
	 * @return Whether the user was authenticated. This base implementation
	 *         returns false if any errors occur, rather than throw an
	 *         exception.
	 */
	public boolean login(HttpServletRequest request,
			HttpServletResponse response, String userId, String password,
			boolean cookie) throws AuthenticatorException {
		User user = null;
		try {
			String uid = userId.toUpperCase();
			user = userManager.get(uid);
		} catch (Exception e) {
			log.debug("Could not find user who tried to login: " + e);
		}
		// check that they can login (they have the USE permission or ADMINISTER
		// permission)
		if (user == null || !user.getIsValid()) {
			log.info("Cannot login user '" + userId
					+ "' as they do not exist or unvalid.");
			if (user == null)
				request.getSession().setAttribute(LOGIN_ERROR_MSG,
						LOGIN_ERROR_CODE0);
			else if (!user.getIsValid())
				request.getSession().setAttribute(LOGIN_ERROR_MSG,
						LOGIN_ERROR_CODE9);
		} else {
			boolean authenticated = false;
			// DB authenticate
			if (!authenticated) {
				authenticated = authenticate(user, password);
			}
			if (authenticated) {
				request.getSession().setAttribute(LOGGED_IN_KEY, user);
				request.getSession().setAttribute(LOGGED_OUT_KEY, null);

				if (user != null) {
					if (cookie && response != null) {
						CookieUtil.setCookie(request, response,
								LOGIN_COOKIE_KEY, CookieUtil.encodePasswordCookie(userId, password, COOKIE_ENCODING),
								AUTOLOGIN_COOKIE_AGE, getCookiePath(request));
					}
					return true;
				} else {
					request.getSession().removeAttribute(LOGGED_IN_KEY);
				}
			} else {
				log.info("Cannot login user '" + userId
						+ "' as they used an incorrect password");
				request.getSession().setAttribute(LOGIN_ERROR_MSG,
						LOGIN_ERROR_CODE1);
			}
		}

		if (response != null
				&& CookieUtil.getCookie(request, LOGIN_COOKIE_KEY) != null) {
			log
					.warn("LoginUser: "
							+ userId
							+ " tried to login but they do not have USE permission or weren't found. Deleting cookie.");

			try {
				CookieUtil.invalidateCookie(request, response,
						LOGIN_COOKIE_KEY, getCookiePath(request));
			} catch (Exception e) {
				log.error("Could not invalidate cookie: " + e, e);
			}
		}

		return false;
	}

	// The following two methods are the only OSUser-specific parts of this
	// class

	/** Uses User's authenticate() to authenticate a user. */
	protected boolean authenticate(User user, String password) {
		boolean bLogin = true;
		return bLogin;
	}

	/**
	 * LDAP auth
	 * 
	 * @param userDn
	 *            Domain user id
	 * @param password
	 * @return
	 */
	/*
	 * public boolean authenticate(ContextSource contextSource, String userDn,
	 * String password) { DirContext ctx = null; try { ctx =
	 * contextSource.getContext(userDn, password);
	 * 
	 * return true; } catch (Exception e) { //Context creation failed -
	 * authentication did not succeed //logger.error("Login failed", e); return
	 * false; } finally { // It is imperative that the created DirContext
	 * instance is always closed LdapUtils.closeContext(ctx); } }
	 */
	public boolean logout(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticatorException {
		request.getSession().setAttribute(LOGGED_IN_KEY, null);
		request.getSession().setAttribute(LOGGED_OUT_KEY, Boolean.TRUE);

		try {
			CookieUtil.invalidateCookie(request, response, LOGIN_COOKIE_KEY,
					getCookiePath(request));
		} catch (Exception e) {
			log.error("Could not invalidate cookie: " + e, e);
		}
		return true;
	}

	/**
	 * Returns the currently logged in user.
	 * 
	 * Warning this method does not necessarily authenticate the user
	 */
	public User getUser(HttpServletRequest request, HttpServletResponse response) {
		User user = null;

		try {
			// when running tests, a request may not neccessarily have a session
			if (request.getSession() != null
					&& request.getSession().getAttribute(LOGGED_OUT_KEY) != null) {
				log.debug("Session found; user already logged in");
				user = null;
			} else if (request.getSession() != null
					&& request.getSession().getAttribute(LOGGED_IN_KEY) != null) {
				log.debug("Session found; user already logged in");
				user = (User) request.getSession().getAttribute(LOGGED_IN_KEY);
			} else {
				// otherwise look for a cookie
				Cookie cookie = CookieUtil.getCookie(request, LOGIN_COOKIE_KEY);

				if (cookie != null) {
					String[] values = CookieUtil.decodePasswordCookie(cookie
							.getValue(), COOKIE_ENCODING);

					if (values != null) {
						String username = values[0];
						String password = values[1];

						if (login(request, response, username, password, false)) {
							log.debug("Logged user in via a cookie");
							return getUser(request);
						}
					}
					log.debug("Cannot log user in via a cookie");
				}
			}
		} catch (Exception e) {
			log.warn("Exception: " + e, e);
		}

		return user;
	}

	/**
	 * 用户是否存在角色。先在全局缓存中查找，没有再到数据库查找
	 * @param user
	 * @param request
	 * @param role
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public boolean hasRole(User user, String role) {
		if (user == null)
			return false;
		List list = hibernateInvoker
				.find(
						"select count(u.id) from User u join u.roles r where u.isValid = 1 and u.id = ? and r.roleId = ?",
						user.getUser_ID(), role);
		if (list == null || list.size() == 0) {
			return false;
		}
		return true;
	}

	/**
	 * Root the login cookie at the same location as the webapp.
	 * 
	 * Anyone wanting a different cookie path policy can override the
	 * authenticator and provide one.
	 */
	protected String getCookiePath(HttpServletRequest request) {
		String path = request.getContextPath();
		if (path == null || path.equals(""))
			return "/";

		// The spec says this should never happen, but just to be sure...
		if (!path.startsWith("/"))
			return "/" + path;

		return path;
	}

	public String getRemoteUser(HttpServletRequest request) {
		User user = getUser(request);
		if (user == null)
			return null;
		return user.getUser_ID();
	}

	public User getUser(HttpServletRequest request) {
		return getUser(request, null);
	}
}
